package com.orcwar.engine.level;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import android.graphics.Rect;
import android.util.Log;

import com.orcwar.engine.OWItem;
import com.orcwar.engine.OWMap;
import com.orcwar.engine.OWPlayer;
import com.orcwar.engine.OWTile;
import com.orcwar.engine.OWUnit;
import com.orcwar.engine.level.OWLevelBuilder.OWLevelParameters;
import com.orcwar.enums.OWItemType;
import com.orcwar.enums.OWTileType;
import com.orcwar.util.Algorithm;
import com.orcwar.util.ComposanteConnexe;
import com.orcwar.util.RandomGenerator;

public class DefaultLevelGenerator implements ILevelGenerator {

	public List<List<OWTile>> buildTiles(OWLevelParameters owLevelParameters,
			int nbEnemies, int nbPlayerUnits) {
		// build tiles
		List<List<OWTile>> tiles = new ArrayList<List<OWTile>>();
		for (int i = 0; i < owLevelParameters.getWidth(); i++) {
			List<OWTile> col = new ArrayList<OWTile>();
			for (int j = 0; j < owLevelParameters.getHeight(); j++) {
				OWTile tile = new OWTile(j + i * owLevelParameters.getWidth(),
						j, i);
				int[] tilesProbaMod = applyModificationsOnProbas(tiles,
						owLevelParameters.getProbas(), i, col, j,
						owLevelParameters.getWidth(),
						owLevelParameters.getHeight());

				// copy and modify probas with neighbor information
				tile.setTileType(RandomGenerator.randomObject(tilesProbaMod,
						OWTileType.values()));
				col.add(tile);
			}
			tiles.add(col);
		}

		// calculate the number of composantesConnexes
		List<ComposanteConnexe> comps = Algorithm
				.countNbComposantesConnexes(tiles);
		if (comps.size() != 1) {
			// more than one zone, REDO!
			return null;
		}

		// check if there is enough tiles to place people
		// player
		if (!(checkIsThereEnoughSpace(nbEnemies, tiles,
				owLevelParameters.getEnemyStart()) && checkIsThereEnoughSpace(
				nbPlayerUnits, tiles, owLevelParameters.getPlayerStart()))) {
			return null;
		}

		return tiles;
	}

	private boolean checkIsThereEnoughSpace(int nbUnits,
			List<List<OWTile>> tiles, Rect zone) {
		int tileCount = 0;
		int minX, maxX, minY, maxY;
		if (zone != null) {
			minX = zone.left;
			minY = zone.top;
			maxX = zone.right;
			maxY = zone.bottom;
		} else {
			// the zone is ALL the map
			minX = 0;
			minY = 0;
			maxX = tiles.get(0).size() - 1;
			maxY = tiles.size() - 1;
		}
		for (int row = minY; row <= maxY; row++) {
			for (int col = minX; col <= maxX; col++) {
				if (tiles.get(row).get(col).canGoThrough()) {
					tileCount++;
				}
			}
		}
		return tileCount >= nbUnits;
	}

	@Override
	public List<OWUnit> buildEnemies(OWLevelParameters owLevelParameters,
			OWPlayer player) {
		List<OWUnit> enemies = new ArrayList<OWUnit>();
		int difficulty = owLevelParameters.getDifficulty();
		int pool = difficulty;
		// get number of enemies
		int nbEnemies = RandomGenerator.randomIntInRange(3,
				Math.min(10, difficulty / 3 + 3));

		int remainingPool = pool;
		for (int i = 0; i < nbEnemies; i++) {
			// random enemy type
			OWUnit enemy = RandomGenerator.randomUnit(player);

			// each enemy get a pool of "points"
			// the greater the pool the best items it can get
			int enemyPool;
			if (i == nbEnemies - 1) {
				// last one
				enemyPool = remainingPool;
			} else {
				enemyPool = RandomGenerator.randomIntInRange(
						-(pool / nbEnemies), (pool * 3) / nbEnemies);
				remainingPool -= enemyPool;
			}

			// generate items with available pool!
			List<Integer> items = new ArrayList<Integer>();
			int remainingItemPool = enemyPool;
			boolean done = false;
			int maxItems = Math.min(10, difficulty / 10 + 2);
			while (!done) {
				if (remainingItemPool == 0) {
					if (items.isEmpty() && Math.random() > 0.5) {
						// 1/2 chance to stop now if pool = 0
						done = true;
					} else {
						done = true;
					}
				} else if (items.size() > maxItems) {
					items.add(remainingItemPool);
					done = true;
				} else {
					// generate an item with itemLevel between -8 and 8
					int amplitude = Math.min(8, difficulty / 6 + 2);
					// actually, if the remaining pool points are positive, we
					// force to get a positive item. If it is negative, we force
					// to get a negative item.
					// This way there is no chance that the item pool is too
					// high when reaching item limit, as the pool always tries
					// to get to 0
					int item = RandomGenerator
							.randomIntInRange(
									-amplitude
											+ (remainingItemPool > 0 ? amplitude
													: 0),
									amplitude
											- (remainingItemPool < 0 ? amplitude
													: 0));
					items.add(item);
					remainingItemPool -= item;
				}

			}
			Log.d(DefaultLevelGenerator.class.getName(), "generated : " + items);
			// generate real items from item levels
			List<OWItem> realItems;
			try {
				realItems = generateRealItems(items);
			} catch (InstantiationException e) {
				e.printStackTrace();
				return new ArrayList<OWUnit>();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
				return new ArrayList<OWUnit>();
			}
			// add items to enemy
			enemy.setItems(realItems);
			enemy.setEnemy(true);
			enemies.add(enemy);
		}
		return enemies;
	}

	@Override
	public void placeUnits(OWMap map, List<OWUnit> enemies,
			List<OWUnit> playerUnits, OWLevelParameters owLevelParameters) {
		// enemies
		placeUnits(map, enemies, owLevelParameters.getEnemyStart());
		// player units
		placeUnits(map, playerUnits, owLevelParameters.getPlayerStart());
	}

	private void placeUnits(OWMap map, List<OWUnit> units, Rect zone) {
		int i = 0;
		int minX, maxX, minY, maxY;
		if (zone != null) {
			minX = zone.left;
			minY = zone.top;
			maxX = zone.right;
			maxY = zone.bottom;
		} else {
			// the zone is ALL the map
			minX = 0;
			minY = 0;
			maxX = map.getTiles().get(0).size() - 1;
			maxY = map.getTiles().size() - 1;
		}

		while (i < units.size()) {
			OWUnit unit = units.get(i);

			int rX = RandomGenerator.randomIntInRange(minX, maxX);
			int rY = RandomGenerator.randomIntInRange(minY, maxY);
			// check tile
			OWTile tile = map.getTiles().get(rY).get(rX);
			if (tile.canGoThrough(unit)) {
				// OK
				tile.setCurrentUnit(unit);
				i++;
				Log.d(DefaultLevelGenerator.class.getName(), "unit " + unit
						+ " placed on " + tile + " x=" + rX + "/y=" + rY);
			}// else KO, rechoose

		}
	}

	private List<OWItem> generateRealItems(List<Integer> items)
			throws InstantiationException, IllegalAccessException {
		ArrayList<OWItem> realItems = new ArrayList<OWItem>();
		int i = 0;
		while (i < items.size()) {
			int quality = items.get(i);
			OWItemType type = OWItemType.getItemOfQuality(quality);
			OWItem existingItem = getExistingItem(type, realItems);
			if (existingItem != null) {
				// item already on unit
				if (type.isStackable()
						&& (existingItem.getStacks() < type.getMaxStacks() || type
								.getMaxStacks() == 0)) {
					// stackable, add a stack
					existingItem.setStacks(existingItem.getStacks() + 1);
					i++;
				} else {
					// not stackable or too much stacks, rechoose
					continue;
				}
			} else {
				// item not already on unit, add it
				OWItem newItem = type.getImpl().newInstance();
				newItem.setType(type);
				realItems.add(newItem);
				i++;
			}

		}

		return realItems;
	}

	private OWItem getExistingItem(OWItemType type, ArrayList<OWItem> realItems) {
		for (OWItem item : realItems) {
			if (item.getType() == type) {
				return item;
			}
		}
		return null;
	}

	private int[] applyModificationsOnProbas(List<List<OWTile>> tiles,
			int[] tilesProba, int i, List<OWTile> col, int j, int w, int h) {
		int[] tilesProbaMod = Arrays.copyOf(tilesProba, tilesProba.length);

		// MODIFICATION TO HAVE MORE SPECIAL TILES IN THE CENTER
		// compute distance to center, center is (width/2,height/2)
		int d = (int) Math
				.sqrt(Math.pow(i - w / 2, 2) + Math.pow(j - h / 2, 2));
		// compute max distance
		int maxD = (int) Math.sqrt(Math.pow(w / 2, 2) + Math.pow(h / 2, 2));
		for (OWTileType t : OWTileType.values()) {
			if (t != OWTileType.GROUND) {
				tilesProbaMod[t.ordinal()] = tilesProbaMod[t.ordinal()]
						+ (maxD / 2 - d) * 4;
			}
		}

		// MODIFICATION TO HAVE MORE CHANCE TO GET A SIMILAR BLOC THAN
		// NEIGHBOURS
		OWTileType upType = null, leftType = null;
		// Get type of neighbors up and left
		if (j != 0) {
			// tiles doesn't yet contain col so we use col variable
			leftType = col.get(j - 1).getTileType();
		}
		if (i != 0) {
			upType = tiles.get(i - 1).get(j).getTileType();
		}
		if (leftType != null) {
			tilesProbaMod[leftType.ordinal()] = tilesProbaMod[leftType
					.ordinal()] * 5;
		}
		if (upType != null) {
			tilesProbaMod[upType.ordinal()] = tilesProbaMod[upType.ordinal()] * 5;
		}

		return tilesProbaMod;
	}

}
